package com.coincalf.wallet.task.syncblock;

import com.coincalf.framework.enums.RechargeStatus;
import com.coincalf.framework.utils.DateUtil;
import com.coincalf.wallet.blockchain.client.LtcClientSingleton;
import com.coincalf.wallet.entity.CoinRecharge;
import com.coincalf.wallet.entity.SyncBlockHeight;
import com.coincalf.wallet.entity.UserAddress;
import com.coincalf.wallet.service.CoinRechargeService;
import com.coincalf.wallet.service.SyncBlockHeightService;
import com.coincalf.wallet.service.UserAddressService;
import com.google.common.collect.Maps;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import wf.bitcoin.javabitcoindrpcclient.BitcoinJSONRPCClient;
import wf.bitcoin.javabitcoindrpcclient.BitcoindRpcClient;

import java.math.BigDecimal;
import java.util.*;

/**
 * 同步莱特币区块 2.5分钟出一个块
 *
 * @author czj
 * @date 2019/5/10 1:24 PM
 */
@Component
@EnableScheduling
@Transactional(rollbackFor = Exception.class)
public class SyncLtcNewBlock {

    private static final Logger log = LoggerFactory.getLogger(SyncBtcNewBlock.class);

    @Autowired
    private UserAddressService userAddressService;
    @Autowired
    private CoinRechargeService rechargeService;
    @Autowired
    private SyncBlockHeightService syncBlockHeightService;


    /**
     * 同步ltc最新区块 2.5分钟一个块
     */
    //@Scheduled(cron = "0 0/2 * * * ?")
    public void syncTask() {
        log.info("开始执行LTC区块扫描任务 当前时间={}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        long startTime = System.currentTimeMillis();
        //从数据库获取已经同步的区块高度
        SyncBlockHeight ltcSyncedBlockHeight = syncBlockHeightService.selectOneByCoinName("LTC");
        if (ltcSyncedBlockHeight == null || ltcSyncedBlockHeight.getSyncedBlockHeight() == null) {
            log.warn("无法找到LTC同步缓存高度");
            return;
        } else if (ltcSyncedBlockHeight.getStatus() == 0) {
            log.info("本次LTC同步状态已关闭");
            return;
        }
        int height = ltcSyncedBlockHeight.getSyncedBlockHeight();

        //rpc节点调用查询当前最新区块高度
        BitcoinJSONRPCClient ltcClient = LtcClientSingleton.getLtcClient();
        int newestBlock = ltcClient.getBlockCount();

        if (height >= newestBlock) {
            log.info("已同步LTC高度:{} 追上最新高度:{} 啦", height, newestBlock);
            return;
        }

        //rpc节点调用查询区块信息
        String blockHash = ltcClient.getBlockHash(height);
        BitcoindRpcClient.Block block = ltcClient.getBlock(blockHash);
        //区块确认数
        int confirmations = block.confirmations();
        //出块时间
        Date blockTime = block.time();
        List<String> txsHash = block.tx();

        //获取所有用户LTC地址
        HashMap<String, Long> userAddress = Maps.newHashMap();
        List<UserAddress> userAddresses = userAddressService.selectListByCoinId(ltcSyncedBlockHeight.getCoinId());
        for (UserAddress address : userAddresses) {
            String chainAddress = address.getAddress();
            userAddress.put(chainAddress, address.getUserId());
        }

        //查询交易详情
        for (String txHash : txsHash) {
            //一笔交易详情
            BitcoindRpcClient.RawTransaction rawTransaction = ltcClient.getRawTransaction(txHash);
            BigDecimal txIn = BigDecimal.ZERO;
            BigDecimal txOut = BigDecimal.ZERO;
            //交易金额
            BigDecimal amount;
            //遍历交易输出,判断是否有用户地址
            //String targetAddress = null;
            Map<String, BigDecimal> targetAddresses = new HashMap<>(4);
            //交易输出
            List<BitcoindRpcClient.RawTransaction.Out> outs = rawTransaction.vOut();
            //判断是否存在用户
            for (BitcoindRpcClient.RawTransaction.Out out : outs) {
                List<String> addresses = out.scriptPubKey().addresses();
                if (!CollectionUtils.isEmpty(addresses)) {
                    String address = addresses.get(0);
                    //判断是否是app内地址
                    if (userAddress.containsKey(address)) {
                        log.info("查询到LTC交易 address={} amount={} txHash={}", address, out.value(), txHash);
                        amount = out.value();
                        targetAddresses.put(address, amount);
                    }
                }
            }

            if (!targetAddresses.isEmpty()) {
                StringBuilder fromAddress = new StringBuilder("");
                //遍历交易输入
                List<BitcoindRpcClient.RawTransaction.In> ins = rawTransaction.vIn();
                for (BitcoindRpcClient.RawTransaction.In in : ins) {
                    BitcoindRpcClient.RawTransaction.Out transactionOutput = in.getTransactionOutput();
                    txIn = txIn.add(transactionOutput.value());
                    List<String> addresses = in.getTransactionOutput().scriptPubKey().addresses();
                    //TODO LTC转账地址是无法确定具体哪一个的，可能存在多个转账地址 可以用英文逗号分隔开
                    fromAddress.append(addresses.get(0)).append(",");
                }
                //矿工费
                BigDecimal fee = txIn.subtract(txOut);

                Set<Map.Entry<String, BigDecimal>> entries = targetAddresses.entrySet();
                String toAddress;
                for (Map.Entry<String, BigDecimal> entry : entries) {
                    toAddress = entry.getKey();
                    amount = entry.getValue();
                    //构建LTC交易表
                    this.addTx(txHash, ltcSyncedBlockHeight.getCoinId(), fromAddress.toString(), toAddress, userAddress.get(toAddress), amount, fee, confirmations, height, blockTime);
                }
            }
        }

        //再次查询mysql最新数据 判断同步任务是否需要执行
        height = height + 1;
        ltcSyncedBlockHeight.setSyncedBlockHeight(height);
        ltcSyncedBlockHeight.setLastUpdateTime(new Date());
        syncBlockHeightService.updateById(ltcSyncedBlockHeight);

        //任务结束
        long endTime = System.currentTimeMillis();
        long spent = (endTime - startTime) / 1000;
        log.info("本次LTC区块同步任务结束，耗时={}秒 height={}", spent, height);
    }

    private void addTx(
            String txHash,
            Long coinId,
            String fromAddress,
            String toAddress,
            Long userId,
            BigDecimal amount,
            BigDecimal fee,
            long confirmations,
            long height,
            Date blockTime
    ) {

        //查询交易是否已经存在
        CoinRecharge recharge = rechargeService.selectOneByCoinIdAndTxIdAndToAddress(
                coinId,
                txHash,
                toAddress
        );
        if (recharge != null) {
            log.info("mysql已存在该LTC充币交易，跳过啦!~ txId={}", txHash);
            return;
        }

        //构建交易表
        CoinRecharge coinRecharge = new CoinRecharge();
        coinRecharge.setCoinId(coinId);
        coinRecharge.setCoinName("LTC");
        coinRecharge.setFromAddress(fromAddress);
        coinRecharge.setAddress(toAddress);
        coinRecharge.setUserId(userId);
        coinRecharge.setTxid(txHash);
        coinRecharge.setMum(amount);
        coinRecharge.setFee(fee);
        coinRecharge.setNum(amount.add(fee));
        coinRecharge.setConfirm(Math.toIntExact(confirmations));
        coinRecharge.setBlockNumber(Math.toIntExact(height));
        coinRecharge.setStatus(RechargeStatus.PENDING.getCode());
        coinRecharge.setCollected(0);
        coinRecharge.setCreated(blockTime);
        coinRecharge.setLastUpdateTime(new Date());
        //写入数据库
        rechargeService.insert(coinRecharge);
        log.info("写入LTC充币交易到数据库啦!~~ coinRecharge={}", coinRecharge);
    }

}
